package com.cd.university.common.obs;

import com.cd.university.common.utils.SpringUtil;
import com.cd.university.controller.form.UploadFileDto;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @author 陈栋
 * @create 2021/12/12 16:43
 */
@Component
@Slf4j
public class ObsMultipartUpload {

    @Autowired
    private ObsClient obsClient;

    /**
     * 云对象存储桶名称
     */
    private static final String bucketName = "spring-study";

    /**
     * 本地文件夹的名称
     */
    private static final String TEMP_FILE_FOLDER = "tempFile";

    /**
     * key是文件的唯一身份，value是文件的信息集合，里面最重要的就是文件的信息
     */
    public static ConcurrentHashMap<String, List<UploadFileDto>> uploadPartLocalFile = new ConcurrentHashMap<>();

    /**
     * 初始化线程池
     */
    public static ExecutorService executorService = Executors.newFixedThreadPool(50);

    public String initiateMultipartUpload(String objectKey) {
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectKey);
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.addUserMetadata("property", "property-value");
        metadata.setContentType("text/plain");
        request.setMetadata(metadata);
        InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);

        String uploadId = result.getUploadId();
        System.out.println("\t" + uploadId);

        return uploadId;
    }

    public boolean uploadPartToLocal(HttpServletRequest httpServletRequest, UploadFileDto uploadFile, String objectKey) {
        // 检查切片是否存在,如果不存在，那么进行下面的操作
        if (!checkIsExists(uploadFile)) {
            // 如果是第一个切片，那么初始化这个切片的map
            synchronized (ObsMultipartUpload.class) {
                if (uploadPartLocalFile.get(uploadFile.getIdentifier())== null) {
                    ArrayList<UploadFileDto> list = new ArrayList<>();
                    uploadPartLocalFile.put(uploadFile.getIdentifier(), list);
                }
            }

            List<UploadFileDto> uploadFileDtoList = uploadPartLocalFile.get(uploadFile.getIdentifier());

            // 拿到文件
            StandardMultipartHttpServletRequest multipartRequest = (StandardMultipartHttpServletRequest) httpServletRequest;
            MultipartFile file = multipartRequest.getFile(multipartRequest.getFileNames().next());

            // 进行MultipartFile转换成File，作为一个缓存文件
            File tempFile = new File(TEMP_FILE_FOLDER + "/" + uploadFile.getChunkNumber() + "-" + uploadFile.getFilename());
            try {
                FileUtils.copyInputStreamToFile(file.getInputStream(), tempFile);
                uploadFileDtoList.add(uploadFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 文件保存成功之后，判断是否所有的切片应该上传完成
        if (checkUploadIsComplete(uploadFile)) {
            ObsMultipartUpload bean = SpringUtil.getBean(ObsMultipartUpload.class);
            bean.partFileMerge(uploadFile,objectKey);
            return true;
        }
        return false;
    }

    public boolean uploadPartToLocal(HttpServletRequest httpServletRequest, UploadFileDto uploadFile) {
        // 检查切片是否存在,如果不存在，那么进行下面的操作
        if (!checkIsExists(uploadFile)) {
            // 如果是第一个切片，那么初始化这个切片的map
            synchronized (ObsMultipartUpload.class) {
                if (uploadPartLocalFile.get(uploadFile.getIdentifier())== null) {
                    ArrayList<UploadFileDto> list = new ArrayList<>();
                    uploadPartLocalFile.put(uploadFile.getIdentifier(), list);
                }
            }

            List<UploadFileDto> uploadFileDtoList = uploadPartLocalFile.get(uploadFile.getIdentifier());

            // 拿到文件
            StandardMultipartHttpServletRequest multipartRequest = (StandardMultipartHttpServletRequest) httpServletRequest;
            MultipartFile file = multipartRequest.getFile(multipartRequest.getFileNames().next());

            // 进行MultipartFile转换成File，作为一个缓存文件
            File tempFile = new File(TEMP_FILE_FOLDER + "/" + uploadFile.getChunkNumber() + "-" + uploadFile.getFilename());
            try {
                FileUtils.copyInputStreamToFile(file.getInputStream(), tempFile);
                uploadFileDtoList.add(uploadFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 文件保存成功之后，判断是否所有的切片应该上传完成
        if (checkUploadIsComplete(uploadFile)) {
            return true;
        }
        return false;
    }

    @Async
    public void ObsUploadPart(File largeFile, UploadFileDto uploadFile, String objectKey) {
        final String uploadId = initiateMultipartUpload(objectKey);

        // 每段上传2MB
        long partSize = 2 * 1024 * 1024L;
        long fileSize = largeFile.length();

        // 计算需要上传的段数
        long partCount = fileSize % partSize == 0 ? fileSize / partSize : fileSize / partSize + 1;

        CopyOnWriteArrayList<PartEtag> partEtags = new CopyOnWriteArrayList<>();

        // 执行并发上传段
        for (int i = 0; i < partCount; i++)
        {
            // 分段在文件中的起始位置
            final long offset = i * partSize;
            // 分段大小
            final long currPartSize = (i + 1 == partCount) ? fileSize - offset : partSize;
            // 分段号
            final int partNumber = i + 1;
            executorService.execute(new Runnable()
            {
                @Override
                public void run()
                {
                    UploadPartRequest uploadPartRequest = new UploadPartRequest();
                    uploadPartRequest.setBucketName(bucketName);
                    uploadPartRequest.setObjectKey(objectKey);
                    uploadPartRequest.setUploadId(uploadId);
                    uploadPartRequest.setFile(largeFile);
                    uploadPartRequest.setPartSize(currPartSize);
                    uploadPartRequest.setOffset(offset);
                    uploadPartRequest.setPartNumber(partNumber);

                    UploadPartResult uploadPartResult;
                    try
                    {
                        uploadPartResult = obsClient.uploadPart(uploadPartRequest);
                        System.out.println("Part#" + partNumber + " done\n");
                        partEtags.add(new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber()));
                    }
                    catch (ObsException e)
                    {
                        e.printStackTrace();
                    }
                }
            });
        }

        // 等待上传完成
        executorService.shutdown();
        // 自旋等待
        while (!executorService.isTerminated())
        {
            try
            {
                executorService.awaitTermination(500, TimeUnit.MILLISECONDS);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }

        // 合并段
        CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, objectKey, uploadId, partEtags);
        CompleteMultipartUploadResult result = obsClient.completeMultipartUpload(completeMultipartUploadRequest);
        log.info( uploadFile.getFilename() + "成功上传,上传结果信息为：" + result.toString());

        while (largeFile.exists()) {
            largeFile.delete();
        }
    }

    /**
     * 本地文件整合
     * @param uploadFileDto 文件信息片
     * @param objectKey 存储到云对象的文件名称
     */
    @Async
    public void partFileMerge(UploadFileDto uploadFileDto, String objectKey) {
        List<UploadFileDto> uploadFileDtos = uploadPartLocalFile.get(uploadFileDto.getIdentifier());
        File mergeFile = new File(TEMP_FILE_FOLDER,uploadFileDto.getFilename());
        BufferedOutputStream os = null;
        try {
            os = new BufferedOutputStream(new FileOutputStream(mergeFile));

            // 遍历所有的文件片
            for (UploadFileDto fileDto : uploadFileDtos) {
                File tempFile = new File(TEMP_FILE_FOLDER,fileDto.getChunkNumber() + "-" + fileDto.getFilename());
                if (tempFile.exists()) {
                    byte[] bytes = FileUtils.readFileToByteArray(tempFile);
                    os.write(bytes);
                    os.flush();
                    tempFile.delete();
                }
            }

            // 上传到华为云
//            ObsMultipartUpload bean = SpringUtil.getBean(ObsMultipartUpload.class);
//            bean.ObsUploadPart(mergeFile,uploadFileDto,objectKey);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 判断该切片是否存在，如果存在就不需要传了，这就是断点续传功能
     * @param uploadFileDto 文件切片
     * @return 是否已经存在
     */
    public boolean checkIsExists(UploadFileDto uploadFileDto) {
        List<UploadFileDto> uploadFileDtos = uploadPartLocalFile.get(uploadFileDto.getIdentifier());
        if (uploadFileDtos != null && uploadFileDtos.contains(uploadFileDto)) {
            return true;
        }
        return false;
    }

    /**
     * 就是利用当前已经上传的切片数量和总的切片数量进行比较
     * @param uploadFile 上传的文件
     * @return 是否上传完成
     */
    private boolean checkUploadIsComplete(UploadFileDto uploadFile) {
        int currPartSize = uploadPartLocalFile.get(uploadFile.getIdentifier()).size();
        return currPartSize == uploadFile.getTotalChunks();
    }
}
